<div id="app">
        <input type="text" v-model="username">
        {{username}}
    </div>
    <script> 
    function Watcher (vm, node, name, nodeType) {
      this.name = name;
      this.node = node;
      this.vm = vm;
      this.nodeType = nodeType;
      this.update = function() {
        if (this.nodeType == 'text') {
          this.node.nodeValue = this.vm.data[this.name];
        }
        if (this.nodeType == 'input') {
          this.node.value = this.vm.data[this.name];
        }
      }
      Dep.target = this;
    }
    
    function Dep () {
      this.subs = []
      this.addSub = function(sub) {
        this.subs.push(sub);
      },
      this.notify = function() {
        this.subs.forEach(function(sub) {
          sub.update();
        });
      }
    }
    
    //记录所有绑定数据的节点
    // let watchers = [];
    
    function observer(vm)
    {
        Object.keys(vm.data).forEach(function(key){
            let dep = new Dep()
            let value = vm.data[key]
            Object.defineProperty(vm.data, key,{
                get(){
                    // 添加订阅者 watcher 到主题对象 Dep
                    dep.addSub(Dep.target)
                    return value
                },
                set(newValue){
                    if(newValue === value) return
                    value = newValue
                    //通过该主题的所有订阅者更新数据
                    dep.notify();
                    // console.log("set了最新值"+value);
                    //遍历节点，通知更新数据（###所有节点都遍历性能差）
                    // watchers.forEach(watcher=>{
                    //   if (watcher.nodeType == 'text') {
                    //     watcher.node.nodeValue = watcher.vm.data[watcher.name]
                    //   }
                    //   if (watcher.nodeType == 'input') {
                    //     watcher.node.value = watcher.vm.data[watcher.name]
                    //   }
                    // })
                }
            })
        })
    }
    
    //编译解析指令
    function compile(node, vm)
    {
        let reg = /\{\{(.*)\}\}/g  //正则匹配页面指令
        //元素节点
        if(node.nodeType === 1)
        {
            let attr = node.attributes;
            //解析节点的属性
            for(let i = 0;i < attr.length; i++)
            {
                if(attr[i].nodeName == 'v-model')
                {
                    //-------------------------------
                    node.addEventListener('input',function(e){
                    vm.data[name] = e.target.value;
                    });
                    //-----------------------------
    
                    let name = attr[i].nodeValue     //获取v-model绑定的属性名
                    //记录绑定数据的元素节点
                    // watchers.push({ name: name, node:node, nodeType: 'input', vm:vm })
                    new Watcher(vm, node, name, 'input')
                    node.value = vm.data[name]       //将data中的值赋给该节点
                    node.removeAttribute('v-model'); //解析完毕不要出现vue指令
                }
            }
        }
        //如果节点类型为text
        if(node.nodeType === 3)
        {
            if(reg.test(node.nodeValue))
            {
                let name = RegExp.$1;//获取匹配到的字符串
                name = name.trim();
    
                //记录绑定数据的文本节点
                // watchers.push({ name:name, node:node, nodeType: 'text', vm:vm })
                new Watcher(vm, node, name, 'text'); 
    
                node.nodeValue = vm.data[name];
            }
        }
    }
    
    function Vue(options)
    {
        //初始化
        let el = options.el
        this.data = options.data
        //监听模型数据变化
        observer(this)
        //将挂载目标劫持 -> 存到节点容器中（数据处理） -> 再放到挂载目标中
        let flag = document.createDocumentFragment()
        let child
        let dom = document.querySelector(el);
        while(child = dom.firstChild){
            compile(child, this)    //挂载目标中的节点挨个过滤（解析指令）
            flag.appendChild(child) //放到DocumentFragment页面渲染就不会显示/劫持
        }
        dom.appendChild(flag)//将DocumentFragment放到挂载目标中
    }
    
    let vm = new Vue({
    el:'#app',
    data:{
        username:'webopenfather',
        age: 5
    }
    })
    </script>